﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Clandestine
{
    public class TickCallback
    {
        public double Duration;
        public double Elapsed;
        public RatioTimedEvent Event;
        public CompletedEvent CompletedEvent;
        public EventWaitHandle CompletedSignal;

        public TickCallback(RatioTimedEvent ev, double duration, CompletedEvent cev)
        {
            Duration = duration;
            Elapsed = 0;
            Event = ev;
            CompletedEvent = cev;
            CompletedSignal = new EventWaitHandle(false, EventResetMode.ManualReset);
        }
    }

    /// <summary>
    /// To do a timed event like walking.
    /// </summary>
    /// <param name="ratio">The ratio of elapsed/duration.</param>
    public delegate void RatioTimedEvent(float ratio, float deltaRatio);

    public delegate void DeltaMsTimedEvent(float deltams);

    public delegate void CompletedEvent();

    public static class Ticker
    {
        private static List<TickCallback> tick = new List<TickCallback>();

        private static float deltaMs;

        private static Thread tickerThread;

        private static DateTime dtB = DateTime.Now.AddMilliseconds(-20);

        public static TickCallback AddTimer(DeltaMsTimedEvent ev)
        {
            TickCallback tc;

            AddTimer((ratio, deltaRatio) => ev(deltaMs), double.PositiveInfinity,
                false, null, out tc);

            return tc;
        }

        public static void AddTimer(RatioTimedEvent ev, double duration)
        {
            AddTimer(ev, duration, true, null);
        }

        public static void AddTimer(RatioTimedEvent ev, double duration, bool wait)
        {
            AddTimer(ev, duration, wait, null);
        }

        public static void AddTimer(RatioTimedEvent ev, double duration, bool wait, CompletedEvent completionEvent)
        {
            TickCallback tcDontCare;
            AddTimer(ev, duration, wait, completionEvent, out tcDontCare);
        }

        public static void AddTimer(RatioTimedEvent ev, double duration, bool wait, CompletedEvent completionEvent, out TickCallback tc)
        {
            TickCallback callback = new TickCallback(ev, duration, completionEvent);
            tc = callback;

            if (!Program.Running)
                return;

            lock (tick)
                tick.Add(callback);
            if (wait)
                callback.CompletedSignal.WaitOne();
        }

        public static void RemoveTickCallback(TickCallback tc)
        {
            lock (tick)
            {
                if (tick.Contains(tc))
                    tick.Remove(tc);
                else
                    Log.w("Tried to remove TickCallback, but it didn't exist in the list!");
            }
        }

        public static void Initialise()
        {
            tickerThread = new Thread(new ThreadStart(loop));
            tickerThread.Start();
        }

        public static void DoOneTick()
        {
            DateTime dtE = DateTime.Now;
            TimeSpan delta = dtE - dtB;

            deltaMs = (float)delta.TotalMilliseconds;

            lock (tick)
                for (int i = 0; i < tick.Count; i++)
                {
                    TickCallback currentTick = tick[i];
                    float prevRatio = (float)(currentTick.Elapsed / currentTick.Duration);
                    currentTick.Elapsed += delta.TotalMilliseconds;

                    if (currentTick.Elapsed >= currentTick.Duration)
                    {
                        currentTick.Elapsed = currentTick.Duration; // Lie, it's for the best. (Otherwise ratios will be >1 occasionally.)
                        if (currentTick.Event != null)
                            currentTick.Event(1, 1 - prevRatio); // One last call, for Mikey.
                        currentTick.CompletedSignal.Set(); // Flag anyone who's listening.
                        if (currentTick.CompletedEvent != null) // Call CompletedEvent if it's there...
                            currentTick.CompletedEvent();
                        tick.Remove(currentTick);
                        continue;
                    }

                    float currRatio = (float)(currentTick.Elapsed / currentTick.Duration);
                    if (currentTick.Event != null)
                        currentTick.Event(currRatio, currRatio - prevRatio);
                }

            dtB = dtE;

        }

        private static void loop()
        {
            while (Program.Running)
            {
                DoOneTick();
                Thread.Sleep(16);
            }
        }

        public static void Deinitialise()
        {
            tickerThread.Abort();
            tickerThread = null;

            // Final shutdown stuffs.
            lock (tick)
            {
                // Can't use foreach as some TickCallbacks are null!
                for (int i = 0; i < tick.Count; i++)
                {
                    if (tick[i].Event != null)
                    {
                        tick[i].Event(1f, 1f - ((float)tick[i].Elapsed));
                        tick[i].CompletedSignal.Set();
                        if (tick[i].CompletedEvent != null)
                            tick[i].CompletedEvent();
                    }
                }
                tick.Clear();
            }
        }
    }
}
